The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Build.PL 35
Changes 019
MANIFEST 18
META.yml 35
Makefile.PL 46
README 1738
dist.ini 11
lib/MetaCPAN/API/Author.pm 13741
lib/MetaCPAN/API/CPANRatings.pm 1060
lib/MetaCPAN/API/Module.pm 6122
lib/MetaCPAN/API/POD.pm 3953
lib/MetaCPAN/API/Release.pm 0102
lib/MetaCPAN/API.pm 4580
t/00-compile.t 11
t/_build_extra_params.t 044
t/author.t 023
t/fetch.t 072
t/module.t 023
t/pod.t 055
t/release.t 036
t/ua.t 023
21 files changed (This is a version diff) 418657
@@ -10,10 +10,12 @@ my %module_build_args = (
     'File::Find' => '0',
     'File::Temp' => '0',
     'Module::Build' => '0.3601',
-    'Test::More' => '0'
+    'Test::Fatal' => '0',
+    'Test::More' => '0',
+    'Test::TinyMocker' => '0'
   },
   'configure_requires' => {
-    'ExtUtils::MakeMaker' => '6.31',
+    'ExtUtils::MakeMaker' => '6.30',
     'Module::Build' => '0.3601'
   },
   'dist_abstract' => 'A comprehensive, DWIM-featured API to MetaCPAN',
@@ -21,7 +23,7 @@ my %module_build_args = (
     'Sawyer X <xsawyerx@cpan.org>'
   ],
   'dist_name' => 'MetaCPAN-API',
-  'dist_version' => '0.02',
+  'dist_version' => '0.20',
   'license' => 'perl',
   'module_name' => 'MetaCPAN::API',
   'recommends' => {},
@@ -1,5 +1,24 @@
 Revision history for MetaCPAN-API
 
+0.20        28.07.11
+            * Add complex (manual) searches to author()/release() + docs.
+            * Add file() as a synonym to module().
+            * Respect content-type.
+            * Allow setting additional params to fetch().
+            * Allow "pauseid" in author via key.
+            * Better check for content-type.
+
+0.11        24.07.11
+            * Correct the POD example and tests.
+            * Update to use a different API path.
+
+0.10        24.07.11
+            * Almost complete rewrite.
+            * Make use of the new beta API.
+            * Remove old API support.
+            * Remove DWIM methods for now.
+            * Include lots of tests.
+
 0.02        13.02.11
             (First stable release!)
             * Add docs (Sawyer X).
@@ -8,8 +8,15 @@ README
 dist.ini
 lib/MetaCPAN/API.pm
 lib/MetaCPAN/API/Author.pm
-lib/MetaCPAN/API/CPANRatings.pm
 lib/MetaCPAN/API/Module.pm
 lib/MetaCPAN/API/POD.pm
+lib/MetaCPAN/API/Release.pm
 t/00-compile.t
+t/_build_extra_params.t
+t/author.t
+t/fetch.t
+t/module.t
+t/pod.t
 t/release-pod-syntax.t
+t/release.t
+t/ua.t
@@ -6,12 +6,14 @@ build_requires:
   File::Find: 0
   File::Temp: 0
   Module::Build: 0.3601
+  Test::Fatal: 0
   Test::More: 0
+  Test::TinyMocker: 0
 configure_requires:
-  ExtUtils::MakeMaker: 6.31
+  ExtUtils::MakeMaker: 6.30
   Module::Build: 0.3601
 dynamic_config: 0
-generated_by: 'Dist::Zilla version 4.102342, CPAN::Meta::Converter version 2.102400'
+generated_by: 'Dist::Zilla version 4.200009, CPAN::Meta::Converter version 2.110580'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -24,4 +26,4 @@ requires:
   JSON: 0
   Try::Tiny: 0
   URI::Escape: 0
-version: 0.02
+version: 0.20
@@ -4,7 +4,7 @@ use warnings;
 
 
 
-use ExtUtils::MakeMaker 6.31;
+use ExtUtils::MakeMaker 6.30;
 
 
 
@@ -15,10 +15,12 @@ my %WriteMakefileArgs = (
     'File::Find' => '0',
     'File::Temp' => '0',
     'Module::Build' => '0.3601',
-    'Test::More' => '0'
+    'Test::Fatal' => '0',
+    'Test::More' => '0',
+    'Test::TinyMocker' => '0'
   },
   'CONFIGURE_REQUIRES' => {
-    'ExtUtils::MakeMaker' => '6.31',
+    'ExtUtils::MakeMaker' => '6.30',
     'Module::Build' => '0.3601'
   },
   'DISTNAME' => 'MetaCPAN-API',
@@ -33,7 +35,7 @@ my %WriteMakefileArgs = (
     'Try::Tiny' => '0',
     'URI::Escape' => '0'
   },
-  'VERSION' => '0.02',
+  'VERSION' => '0.20',
   'test' => {
     'TESTS' => 't/*.t'
   }
@@ -2,21 +2,21 @@ NAME
     MetaCPAN::API - A comprehensive, DWIM-featured API to MetaCPAN
 
 VERSION
-    version 0.02
+    version 0.20
 
 SYNOPSIS
-        my $mcpan   = MetaCPAN::API->new();
-        my @authors = $mcpan->search_author_pauseid('XSAWYERX');
-        my @dists   = $mcpan->search_dist("MetaCPAN");
+        my $mcpan  = MetaCPAN::API->new();
+        my $author = $mcpan->author('XSAWYERX');
+        my $dist   = $mcpan->release( distribution => 'MetaCPAN::API' );
 
 DESCRIPTION
-    This is a complete API-compliant interface to MetaCPAN
+    This is a hopefully-complete API-compliant interface to MetaCPAN
     (http://search.metacpan.org) with DWIM capabilities, to make your life
     easier.
 
     This module has three purposes:
 
-    *   Provide 100% of the MetaCPAN API
+    *   Provide 100% of the beta MetaCPAN API
 
         This module will be updated regularly on every MetaCPAN API change,
         and intends to provide the user with as much of the API as possible,
@@ -24,7 +24,10 @@ DESCRIPTION
         do it.
 
         Because of this design decision, this module has an official
-        MetaCPAN namespace.
+        MetaCPAN namespace with the blessing of the MetaCPAN developers.
+
+        Notice this module currently only provides the beta API, not the old
+        soon-to-be-deprecated API.
 
     *   Be lightweight, to allow flexible usage
 
@@ -67,19 +70,13 @@ ATTRIBUTES
     tunnel it through a local port, or any of those stuff, you would want to
     change this.
 
-    Default: *http://api.metacpan.org*.
+    Default: *http://beta.api.metacpan.org*.
 
     This attribute is read-only (immutable), meaning that once it's set on
     initialize (via "new()"), you cannot change it. If you need to, create a
     new instance of MetaCPAN::API. Why is it immutable? Because it's better.
 
   ua
-        my $mcpan = MetaCPAN::API->new(
-            ua => HTTP::Tiny->new(
-                %extra_args,
-            ),
-        );
-
     This attribute is used to contain the user agent used for running the
     REST request to the server. It is specifically set to HTTP::Tiny, so if
     you want to set it manually, make sure it's of HTTP::Tiny.
@@ -90,10 +87,34 @@ ATTRIBUTES
     initialize (via "new()"), you cannot change it. If you need to, create a
     new instance of MetaCPAN::API. Why is it immutable? Because it's better.
 
+  ua_args
+        my $mcpan = MetaCPAN::API->new(
+            ua_args => [ agent => 'MyAgent' ],
+        );
+
+    The arguments that will be given to the HTTP::Tiny user agent.
+
+    This attribute is read-only (immutable), meaning that once it's set on
+    initialize (via "new()"), you cannot change it. If you need to, create a
+    new instance of MetaCPAN::API. Why is it immutable? Because it's better.
+
 METHODS
-    Currently methods are documented by their respected namespace. In the
-    future you might find some of the documentation ported (or copy-pasted)
-    here for your convenience.
+  fetch
+        my $result = $mcpan->fetch('/release/distribution/Moose');
+
+        # with parameters
+        my $more = $mcpan->fetch(
+            '/release/distribution/Moose',
+            param => 'value',
+        );
+
+    This is a helper method for API implementations. It fetches a path from
+    MetaCPAN, decodes the JSON from the content variable and returns it.
+
+    You don't really need to use it, but you can in case you want to write
+    your own extension implementation to MetaCPAN::API.
+
+    It accepts an additional hash as "GET" parameters.
 
 AUTHOR
       Sawyer X <xsawyerx@cpan.org>
@@ -4,7 +4,7 @@ license = Perl_5
 copyright_holder = Sawyer X
 copyright_year   = 2011
 
-version = 0.02
+version = 0.20
 
 [@Basic]
 [PodSyntaxTests]
@@ -2,88 +2,41 @@ use strict;
 use warnings;
 package MetaCPAN::API::Author;
 BEGIN {
-  $MetaCPAN::API::Author::VERSION = '0.02';
+  $MetaCPAN::API::Author::VERSION = '0.20';
 }
 # ABSTRACT: Author information for MetaCPAN::API
 
+use Carp;
 use Any::Moose 'Role';
-use URI::Escape;
 
-requires '_http_req';
-
-has author_prefix => (
-    is      => 'ro',
-    isa     => 'Str',
-    default => 'author',
-);
-
-sub search_author {
+# /author/{author}
+sub author {
     my $self = shift;
-    my $term = shift;
-    my @hits = ();
-
-    # clean leading/trailing spaces
-    $term =~ s/^\s+//;
-    $term =~ s/\s+$//;
-
-    # if there are no spaces, it might be a PAUSE ID
-    if ( $term !~ /\s/ ) {
-        push @hits, $self->_get_hits( $self->search_author_pauseid($term) );
+    my ( $pause_id, $url, %extra_opts );
+
+    if ( @_ == 1 ) {
+        $url = 'author/' . shift;
+    } elsif ( @_ == 2 ) {
+        my %opts = @_;
+
+        if ( defined $opts{'pauseid'} ) {
+            $url = "author/" . $opts{'pauseid'};
+        } elsif ( defined $opts{'search'} ) {
+            my $search_opts = $opts{'search'};
+
+            ref $search_opts && ref $search_opts eq 'HASH'
+                or croak "'search' key must be hashref";
+
+            %extra_opts = %{$search_opts};
+            $url        = 'author/_search';
+        } else {
+            croak 'Unknown option given';
+        }
+    } else {
+        croak 'Please provide an author PAUSEID or a "search"';
     }
 
-    # search by name
-    push @hits, $self->_get_hits( $self->search_author_name($term) );
-
-    # search by wildcard
-    push @hits, $self->_get_hits( $self->search_author_wildcard($term) );
-
-    # remove uniques
-    my %seen   = ();
-    my @unique = grep { ! $seen{ $_->{'_id'} }++ } @hits;
-
-    return @unique;
-}
-
-# http://api.metacpan.org/author/DROLSKY
-sub search_author_pauseid {
-    my $self    = shift;
-    my $pauseid = shift;
-    my $base    = $self->base_url;
-    my $prefix  = $self->author_prefix;
-    my $url     = "$base/$prefix/$pauseid";
-    my $result  = $self->_http_req($url);
-
-    return $result;
-}
-
-# http://api.metacpan.org/author/_search?q=name:Dave
-# http://api.metacpan.org/author/_search?q=name:%22dave%20rolsky%22
-sub search_author_name {
-    my $self   = shift;
-    my $name   = shift;
-    my $base   = $self->base_url;
-    my $prefix = $self->author_prefix;
-
-    # escape letters, specifying the regex because by default
-    # it will not escape quotations, which it should
-    $name = uri_escape( $name, q{^A-Za-z0-9\-\._~} );
-
-    my $url    = "$base/$prefix/_search?q=name:$name";
-    my $result = $self->_http_req($url);
-
-    return $result;
-}
-
-# http://api.metacpan.org/author/_search?q=author:D*
-sub search_author_wildcard {
-    my $self   = shift;
-    my $term   = shift; # you decide on the wildcard when you call the method
-    my $base   = $self->base_url;
-    my $prefix = $self->author_prefix;
-    my $url    = "$base/$prefix/_search?q=author:$term";
-    my $result = $self->_http_req($url);
-
-    return $result;
+    return $self->fetch( $url, %extra_opts );
 }
 
 1;
@@ -98,79 +51,30 @@ MetaCPAN::API::Author - Author information for MetaCPAN::API
 
 =head1 VERSION
 
-version 0.02
+version 0.20
 
 =head1 DESCRIPTION
 
-This role provides MetaCPAN::API with several methods to get the author
-information.
-
-=head1 ATTRIBUTES
-
-=head2 author_prefix
-
-This attribute helps set the path to the author requests in the REST API.
-You will most likely never have to touch this as long as you have an updated
-version of MetaCPAN::API.
-
-Default: I<author>.
-
-This attribute is read-only (immutable), meaning that once it's set on
-initialize (via C<new()>), you cannot change it. If you need to, create a
-new instance of MetaCPAN::API. Why is it immutable? Because it's better.
+This role provides MetaCPAN::API with fetching information about authors.
 
 =head1 METHODS
 
-=head2 search_author
-
-    my @authors = $mcpan->search_author('Dave');
-
-This method is the DWIM interface for the author searches. It tries to do what
-you probably want. It does so in the following steps:
-
-=over 4
-
-=item 1. Trimming leading and trailing spaces
-
-=item 2. Checks if it's possibly a PAUSE ID
-
-If there are no spaces, meaning it's a single word, it's assumed to optionally
-be a PAUSE ID, so it searches it as a PAUSE ID.
-
-=item 3. Searches by author name
-
-As if you gave I<"Olaf Alders"> as the search.
-
-=item 4. Searches by wildcard
-
-As if you gave I<"Olaf"> but want to find anything with I<Olaf> in it.
-
-=back
-
-It stacks the results on top of each other, so you find the PAUSE ID (if there
-is one) first, the full name search second and the wildcards last. The purpose
-is to try to get as accurate results as possible first time around.
-
-Feel free to submit patches to improve this!
-
-=head2 search_author_pauseid
-
-    my $author = $mcpan->search_author_pauseid('XSAWYERX');
-
-Searches MetaCPAN for a specific PAUSE ID.
-
-=head2 search_author_name
-
-    my $author = $mcpan->search_author_name('Sawyer X');
+=head2 author
 
-Searches MetaCPAN for a specific name.
+    my $result1 = $mcpan->author('XSAWYERX');
+    my $result2 = $mcpan->author( pauseid => 'XSAWYERX' );
 
-=head2 search_author_wildcard
+Searches MetaCPAN for a specific author.
 
-    my $author = $mcpan->search_author_wildcard('Dave');
+You can do complex searches using 'search' parameter:
 
-Searches MetaCPAN for an author using a string, and full wildcard on both
-sides. Equivalent to searching I<*Dave*>.
+    # example lifted from MetaCPAN docs
+    my $result = $mcpan->author(
+        search => {
+            q    => "profile.name:twitter',
+            size => 1,
+        },
+    );
 
 =head1 AUTHOR
 
@@ -1,106 +0,0 @@
-use strict;
-use warnings;
-package MetaCPAN::API::CPANRatings;
-BEGIN {
-  $MetaCPAN::API::CPANRatings::VERSION = '0.02';
-}
-# ABSTRACT: CPAN Ratings information for MetaCPAN::API
-
-use Any::Moose 'Role';
-
-requires '_http_req';
-
-has cpanratings_prefix => (
-    is      => 'ro',
-    isa     => 'Str',
-    default => 'cpanratings',
-);
-
-# http://api.metacpan.org/cpanratings/Moose
-sub search_cpanratings_exact {
-    my $self    = shift;
-    my $dist    = shift;
-    my $base    = $self->base_url;
-    my $prefix  = $self->cpanratings_prefix;
-    my $url     = "$base/$prefix/$dist";
-    my $result  = $self->_http_req($url);
-
-    return $result;
-}
-
-# http://api.metacpan.org/cpanratings/_search?q=dist:Moose
-sub search_cpanratings_like {
-    my $self   = shift;
-    my $dist   = shift;
-    my $base   = $self->base_url;
-    my $prefix = $self->cpanratings_prefix;
-    my $url    = "$base/$prefix/_search?q=dist:$dist";
-    my $result = $self->_http_req($url);
-
-    return $result;
-}
-
-1;
-
-
-
-=pod
-
-=head1 NAME
-
-MetaCPAN::API::CPANRatings - CPAN Ratings information for MetaCPAN::API
-
-=head1 VERSION
-
-version 0.02
-
-=head1 DESCRIPTION
-
-This role provides MetaCPAN::API with several methods to get the CPAN Ratings
-information.
-
-=head1 ATTRIBUTES
-
-=head2 cpanratings_prefix
-
-This attribute helps set the path to the CPAN ratings requests in the REST API.
-You will most likely never have to touch this as long as you have an updated
-version of MetaCPAN::API.
-
-Default: I<cpanratings>.
-
-This attribute is read-only (immutable), meaning that once it's set on
-initialize (via C<new()>), you cannot change it. If you need to, create a
-new instance of MetaCPAN::API. Why is it immutable? Because it's better.
-
-=head1 METHODS
-
-=head2 search_cpanratings_exact
-
-    my $result = $mcpan->search_cpanratings_exact('Moose');
-
-Search for a CPAN Ratings entry about a specific distribution.
-
-=head2 search_cpanratings_like
-
-    my $result = $mcpan->search_cpanratings_like('Moose');
-
-Search for a CPAN Ratings entry with anything that has the string you gave in
-it. Searching for I<"Moose"> is equivalent to anything that has I<Moose> in it.
-
-=head1 AUTHOR
-
-  Sawyer X <xsawyerx@cpan.org>
-
-=head1 COPYRIGHT AND LICENSE
-
-This software is copyright (c) 2011 by Sawyer X.
-
-This is free software; you can redistribute it and/or modify it under
-the same terms as the Perl 5 programming language system itself.
-
-=cut
-
-
-__END__
-
@@ -2,50 +2,26 @@ use strict;
 use warnings;
 package MetaCPAN::API::Module;
 BEGIN {
-  $MetaCPAN::API::Module::VERSION = '0.02';
+  $MetaCPAN::API::Module::VERSION = '0.20';
 }
-# ABSTRACT: Module and dist information for MetaCPAN::API
+# ABSTRACT: Module information for MetaCPAN::API
 
+use Carp;
 use Any::Moose 'Role';
 
-requires '_http_req';
-
-has module_prefix => (
-    is      => 'ro',
-    isa     => 'Str',
-    default => 'module',
-);
-
-# http://api.metacpan.org/module/_search?q=dist:moose
-sub search_dist {
-    my $self     = shift;
-    my $dist     = shift;
-    my %req_opts = @_;
-    my $base     = $self->base_url;
-    my $prefix   = $self->module_prefix;
-    my $url      = "$base/$prefix/_search?q=distname:$dist";
-    my @hits     = $self->_get_hits(
-        $self->_http_req( $url, \%req_opts )
-    );
-
-    return @hits;
-}
+# /module/{module}
+sub module {
+    my $self = shift;
+    my $name = shift;
+
+    $name or croak 'Please provide a module name';
 
-# http://api.metacpan.org/module/Moose
-sub search_module {
-    my $self     = shift;
-    my $module   = shift;
-    my %req_opts = @_ || ();
-    my $base     = $self->base_url;
-    my $prefix   = $self->module_prefix;
-    my $url      = "$base/$prefix/$module";
-    my @hits     = $self->_get_hits(
-        $self->_http_req( $url, \%req_opts )
-    );
-
-    return @hits;
+    return $self->fetch("module/$name");
 }
 
+# file() is a synonym of module
+sub file { goto &module }
+
 1;
 
 
@@ -54,44 +30,29 @@ sub search_module {
 
 =head1 NAME
 
-MetaCPAN::API::Module - Module and dist information for MetaCPAN::API
+MetaCPAN::API::Module - Module information for MetaCPAN::API
 
 =head1 VERSION
 
-version 0.02
+version 0.20
 
 =head1 DESCRIPTION
 
-This role provides MetaCPAN::API with several methods to get the module and
-dist information.
-
-=head1 ATTRIBUTES
+This role provides MetaCPAN::API with fetching information about modules.
 
-=head2 module_prefix
-
-This attribute helps set the path to the module and dist requests in the REST
-API. You will most likely never have to touch this as long as you have an
-updated version of MetaCPAN::API.
-
-Default: I<module>.
-
-This attribute is read-only (immutable), meaning that once it's set on
-initialize (via C<new()>), you cannot change it. If you need to, create a
-new instance of MetaCPAN::API. Why is it immutable? Because it's better.
+More specifically, this returns the C<.pm> file of that module.
 
 =head1 METHODS
 
-=head2 search_dist
-
-    my @dists = $mcpan->search_dist('Moose');
+=head2 module
 
-Searches MetaCPAN for a dist.
+    my $result = $mcpan->module('MetaCPAN::API');
 
-=head2 search_module
+Searches MetaCPAN and returns a module's C<.pm> file.
 
-    my @modules = $mcpan->search_module('Moose');
+=head2 file
 
-Searches MetaCPAN for a module.
+A synonym of C<module>.
 
 =head1 AUTHOR
 
@@ -2,30 +2,51 @@ use strict;
 use warnings;
 package MetaCPAN::API::POD;
 BEGIN {
-  $MetaCPAN::API::POD::VERSION = '0.02';
+  $MetaCPAN::API::POD::VERSION = '0.20';
 }
 # ABSTRACT: POD information for MetaCPAN::API
 
+use Carp;
 use Any::Moose 'Role';
 
-requires '_http_req';
-
-has pod_prefix => (
-    is      => 'ro',
-    isa     => 'Str',
-    default => 'pod',
-);
-
-# http://api.metacpan.org/pod/AAA::Demo
-sub search_pod {
-    my $self    = shift;
-    my $dist    = shift;
-    my $base    = $self->base_url;
-    my $prefix  = $self->pod_prefix;
-    my $url     = "$base/$prefix/$dist";
-    my $result  = $self->_http_req($url);
-
-    return $result;
+# /pod/{module}
+# /pod/{author}/{release}/{path}
+sub pod {
+    my $self  = shift;
+    my %opts  = @_ ? @_ : ();
+    my $url   = '';
+    my $error = "Either provide 'module' or 'author and 'release' and 'path'";
+
+    %opts or croak $error;
+
+    if ( defined ( my $module = $opts{'module'} ) ) {
+        $url = "pod/$module";
+    } elsif (
+        defined ( my $author  = $opts{'author'}  ) &&
+        defined ( my $release = $opts{'release'} ) &&
+        defined ( my $path    = $opts{'path'}    )
+      ) {
+        $url = "pod/$author/$release/$path";
+    } else {
+        croak $error;
+    }
+
+    # check content-type
+    my %extra = ();
+    if ( defined ( my $type = $opts{'content-type'} ) ) {
+        $type =~ m{^ text/ (?: html|plain|x-pod|x-markdown ) $}x
+            or croak 'Incorrect content-type provided';
+
+        $extra{'content-type'} = $type;
+    }
+
+    $url = $self->base_url . "/$url";
+
+    my $result = $self->ua->get( $url, \%extra );
+    $result->{'success'}
+        or croak "Failed to fetch '$url': " . $result->{'reason'};
+
+    return $result->{'content'};
 }
 
 1;
@@ -40,34 +61,27 @@ MetaCPAN::API::POD - POD information for MetaCPAN::API
 
 =head1 VERSION
 
-version 0.02
+version 0.20
 
 =head1 DESCRIPTION
 
-This role provides MetaCPAN::API with several methods to get the CPAN Ratings
-information.
-
-=head1 ATTRIBUTES
-
-=head2 pod_prefix
-
-This attribute helps set the path to the POD requests in the REST API.
-You will most likely never have to touch this as long as you have an updated
-version of MetaCPAN::API.
-
-Default: I<pod>.
-
-This attribute is read-only (immutable), meaning that once it's set on
-initialize (via C<new()>), you cannot change it. If you need to, create a
-new instance of MetaCPAN::API. Why is it immutable? Because it's better.
+This role provides MetaCPAN::API with fetching POD information about modules
+and distribution releases.
 
 =head1 METHODS
 
-=head2 search_pod
+=head2 pod
+
+    my $result = $mcpan->pod( pod => 'Moose' );
 
-    my $result = $mcpan->search_pod('MetaCPAN::API');
+    # or
+    my $result = $mcpan->pod(
+        author  => 'DOY',
+        release => 'Moose-2.0201',
+        path    => 'lib/Moose.pm',
+    );
 
-Search for the POD of a specific module.
+Searches MetaCPAN for a module or a specific release and returns the POD.
 
 =head1 AUTHOR
 
@@ -0,0 +1,102 @@
+use strict;
+use warnings;
+package MetaCPAN::API::Release;
+BEGIN {
+  $MetaCPAN::API::Release::VERSION = '0.20';
+}
+# ABSTRACT: Distribution and releases information for MetaCPAN::API
+
+use Carp;
+use Any::Moose 'Role';
+
+# /release/{distribution}
+# /release/{author}/{release}
+sub release {
+    my $self  = shift;
+    my %opts  = @_ ? @_ : ();
+    my $url   = '';
+    my $error = "Either provide 'distribution', or 'author' and 'release', " .
+                "or 'search'";
+
+    %opts or croak $error;
+
+    my %extra_opts = ();
+
+    if ( defined ( my $dist = $opts{'distribution'} ) ) {
+        $url = "release/$dist";
+    } elsif (
+        defined ( my $author  = $opts{'author'}  ) &&
+        defined ( my $release = $opts{'release'} )
+      ) {
+        $url = "release/$author/$release";
+    } elsif ( defined ( my $search_opts = $opts{'search'} ) ) {
+        ref $search_opts && ref $search_opts eq 'HASH'
+            or croak $error;
+
+        %extra_opts = %{$search_opts};
+        $url        = 'release/_search';
+    } else {
+        croak $error;
+    }
+
+    return $self->fetch( $url, %extra_opts );
+}
+
+1;
+
+
+
+=pod
+
+=head1 NAME
+
+MetaCPAN::API::Release - Distribution and releases information for MetaCPAN::API
+
+=head1 VERSION
+
+version 0.20
+
+=head1 DESCRIPTION
+
+This role provides MetaCPAN::API with fetching information about distribution
+and releases.
+
+=head1 METHODS
+
+=head2 release
+
+    my $result = $mcpan->release( distribution => 'Moose' );
+
+    # or
+    my $result = $mcpan->release( author => 'DOY', release => 'Moose-2.0001' );
+
+Searches MetaCPAN for a dist.
+
+You can do complex searches using 'search' parameter:
+
+    # example lifted from MetaCPAN docs
+    my $result = $mcpan->release(
+        search => {
+            author => "OALDERS AND ",
+            filter => "status:latest",
+            fields => "name",
+            size   => 1,
+        },
+    );
+
+=head1 AUTHOR
+
+  Sawyer X <xsawyerx@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2011 by Sawyer X.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+
+__END__
+
@@ -2,27 +2,29 @@ use strict;
 use warnings;
 package MetaCPAN::API;
 BEGIN {
-  $MetaCPAN::API::VERSION = '0.02';
+  $MetaCPAN::API::VERSION = '0.20';
 }
 # ABSTRACT: A comprehensive, DWIM-featured API to MetaCPAN
 
 use Any::Moose;
-use JSON;
+
 use Carp;
+use JSON;
 use Try::Tiny;
 use HTTP::Tiny;
+use URI::Escape 'uri_escape';
 
 with qw/
     MetaCPAN::API::Author
-    MetaCPAN::API::CPANRatings
     MetaCPAN::API::Module
     MetaCPAN::API::POD
+    MetaCPAN::API::Release
 /;
 
 has base_url => (
     is      => 'ro',
     isa     => 'Str',
-    default => 'http://api.metacpan.org',
+    default => 'http://api.metacpan.org/v0',
 );
 
 has ua => (
@@ -31,41 +33,50 @@ has ua => (
     lazy_build => 1,
 );
 
+has ua_args => (
+    is      => 'ro',
+    isa     => 'ArrayRef',
+    default => sub { [] },
+);
+
 sub _build_ua {
-    return HTTP::Tiny->new;
+    my $self = shift;
+
+    return HTTP::Tiny->new( @{ $self->ua_args } );
 }
 
-sub _get_hits {
-    my $self     = shift;
-    my $response = shift;
-    my @hits     = ();
+sub fetch {
+    my $self    = shift;
+    my $url     = shift;
+    my $extra   = $self->_build_extra_params(@_);
+    my $base    = $self->base_url;
+    my $req_url = $extra ? "$base/$url?$extra" : "$base/$url";
 
-    try {
-        # a single search might return partial JSON data, but no hits,
-        # search dist for "Moose", or author for "Dave", it will come up
-        # in that case, we need to check for a _source key
+    my $result  = $self->ua->get($req_url);
+    my $decoded_result;
 
-        my $content = decode_json $response->{'content'};
+    $result->{'success'}
+        or croak "Failed to fetch '$url': " . $result->{'reason'};
 
-        if ( exists $content->{'hits'}{'hits'} ) {
-            @hits = @{ $content->{'hits'}{'hits'} };
-        } elsif ( exists $content->{'_source'} ) {
-            @hits = $content;
-        }
-    } catch {
-        croak 'There was an error decoding response from MetaCPAN.';
-    };
+    defined ( my $content = $result->{'content'} )
+        or croak 'Missing content in return value';
 
-    return @hits;
+    try   { $decoded_result = decode_json $content }
+    catch { croak "Couldn't decode '$content': $_" };
+
+    return $decoded_result;
 }
 
-sub _http_req {
-    my $self               = shift;
-    my ( $url, $req_opts ) = @_;
+sub _build_extra_params {
+    my $self = shift;
+
+    @_ % 2 == 0
+        or croak 'Incorrect number of params, must be key/value';
 
-    my $res = $self->ua->request( 'GET', $url, $req_opts );
+    my %extra = @_;
+    my $extra = join '&', map { "$_=" . uri_escape($extra{$_}) } keys %extra;
 
-    return $res;
+    return $extra;
 }
 
 1;
@@ -80,30 +91,34 @@ MetaCPAN::API - A comprehensive, DWIM-featured API to MetaCPAN
 
 =head1 VERSION
 
-version 0.02
+version 0.20
 
 =head1 SYNOPSIS
 
-    my $mcpan   = MetaCPAN::API->new();
-    my @authors = $mcpan->search_author_pauseid('XSAWYERX');
-    my @dists   = $mcpan->search_dist("MetaCPAN");
+    my $mcpan  = MetaCPAN::API->new();
+    my $author = $mcpan->author('XSAWYERX');
+    my $dist   = $mcpan->release( distribution => 'MetaCPAN::API' );
 
 =head1 DESCRIPTION
 
-This is a complete API-compliant interface to MetaCPAN
+This is a hopefully-complete API-compliant interface to MetaCPAN
 (http://search.metacpan.org) with DWIM capabilities, to make your life easier.
 
 This module has three purposes:
 
 =over 4
 
-=item * Provide 100% of the MetaCPAN API
+=item * Provide 100% of the beta MetaCPAN API
 
 This module will be updated regularly on every MetaCPAN API change, and intends
 to provide the user with as much of the API as possible, no shortcuts. If it's
 documented in the API, you should be able to do it.
 
-Because of this design decision, this module has an official MetaCPAN namespace.
+Because of this design decision, this module has an official MetaCPAN namespace
+with the blessing of the MetaCPAN developers.
+
+Notice this module currently only provides the beta API, not the old
+soon-to-be-deprecated API.
 
 =item * Be lightweight, to allow flexible usage
 
@@ -147,7 +162,7 @@ MetaCPAN is accessible. By default it's already set correctly, but if you're
 running a local instance of MetaCPAN, or use a local mirror, or tunnel it
 through a local port, or any of those stuff, you would want to change this.
 
-Default: I<http://api.metacpan.org>.
+Default: I<http://beta.api.metacpan.org>.
 
 This attribute is read-only (immutable), meaning that once it's set on
 initialize (via C<new()>), you cannot change it. If you need to, create a
@@ -155,12 +170,6 @@ new instance of MetaCPAN::API. Why is it immutable? Because it's better.
 
 =head2 ua
 
-    my $mcpan = MetaCPAN::API->new(
-        ua => HTTP::Tiny->new(
-            %extra_args,
-        ),
-    );
-
 This attribute is used to contain the user agent used for running the REST
 request to the server. It is specifically set to L<HTTP::Tiny>, so if you
 want to set it manually, make sure it's of HTTP::Tiny.
@@ -171,11 +180,37 @@ This attribute is read-only (immutable), meaning that once it's set on
 initialize (via C<new()>), you cannot change it. If you need to, create a
 new instance of MetaCPAN::API. Why is it immutable? Because it's better.
 
+=head2 ua_args
+
+    my $mcpan = MetaCPAN::API->new(
+        ua_args => [ agent => 'MyAgent' ],
+    );
+
+The arguments that will be given to the L<HTTP::Tiny> user agent.
+
+This attribute is read-only (immutable), meaning that once it's set on
+initialize (via C<new()>), you cannot change it. If you need to, create a
+new instance of MetaCPAN::API. Why is it immutable? Because it's better.
+
 =head1 METHODS
 
-Currently methods are documented by their respected namespace. In the future you
-might find some of the documentation ported (or copy-pasted) here for your
-convenience.
+=head2 fetch
+
+    my $result = $mcpan->fetch('/release/distribution/Moose');
+
+    # with parameters
+    my $more = $mcpan->fetch(
+        '/release/distribution/Moose',
+        param => 'value',
+    );
+
+This is a helper method for API implementations. It fetches a path from
+MetaCPAN, decodes the JSON from the content variable and returns it.
+
+You don't really need to use it, but you can in case you want to write your
+own extension implementation to MetaCPAN::API.
+
+It accepts an additional hash as C<GET> parameters.
 
 =head1 AUTHOR
 
@@ -29,7 +29,7 @@ plan tests => scalar(@modules) + scalar(@scripts);
     # fake home for cpan-testers
     # no fake requested ## local $ENV{HOME} = tempdir( CLEANUP => 1 );
 
-    like( qx{ $^X -Ilib -e "use $_; print '$_ ok'" }, qr/^\s*$_ ok/s, "$_ loaded ok" )
+    is( qx{ $^X -Ilib -e "use $_; print '$_ ok'" }, "$_ ok", "$_ loaded ok" )
         for sort @modules;
 
     SKIP: {
@@ -0,0 +1,44 @@
+#!perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 5;
+use Test::Fatal;
+use MetaCPAN::API;
+
+my $mcpan = MetaCPAN::API->new;
+
+like(
+    exception { $mcpan->_build_extra_params('one') },
+    qr/^Incorrect number of params, must be key\/value/,
+    'Check for key/value params',
+);
+
+my $output;
+is(
+    exception { $output = $mcpan->_build_extra_params },
+    undef,
+    'No exception or problem on empty args',
+);
+
+is(
+    $output,
+    '',
+    'No output either',
+);
+
+# regular
+is(
+    $mcpan->_build_extra_params( param1 => 'one', param2 => 'two' ),
+    'param2=two&param1=one',
+    'Basic params',
+);
+
+# throw some symbols in there
+is(
+    $mcpan->_build_extra_params( param1 => 'one', param2 => 'two space' ),
+    'param2=two%20space&param1=one',
+    'Escaping HTML in params',
+);
+
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More tests => 4;
+use Test::Fatal;
+use MetaCPAN::API;
+
+my $mcpan = MetaCPAN::API->new;
+
+isa_ok( $mcpan, 'MetaCPAN::API' );
+can_ok( $mcpan, 'author'        );
+
+# missing input
+like(
+    exception { $mcpan->author },
+    qr/^Please provide an author PAUSEID/,
+    'Missing any information',
+);
+
+my $result = $mcpan->author('DOY');
+ok( $result, 'Got result' );
+
@@ -0,0 +1,72 @@
+#!perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 13;
+use Test::Fatal;
+use Test::TinyMocker;
+use MetaCPAN::API;
+
+my $mcpan = MetaCPAN::API->new;
+isa_ok( $mcpan, 'MetaCPAN::API' );
+
+my $url  = 'release/distribution/hello';
+my $flag = 0;
+
+mock 'HTTP::Tiny'
+    => methods 'get'
+    => should {
+        my $self = shift;
+        isa_ok( $self, 'HTTP::Tiny' );
+        is( $_[0], $mcpan->base_url . "/$url", 'Correct URL' );
+
+        $flag++ == 0 and return {
+            success => 1,
+            content => '{"test":"test"}',
+        };
+
+        $flag++ == 2 and return {
+            success => 1,
+        };
+
+        return {
+            success => 1,
+            content => 'string',
+        };
+    };
+
+my $result = $mcpan->fetch($url);
+is_deeply( $result, { test => 'test' }, 'Correct result' );
+
+like(
+    exception { $mcpan->fetch($url) },
+    qr/^Missing content in return value/,
+    'When content is missing',
+);
+
+like(
+    exception { $mcpan->fetch($url) },
+    qr/^Couldn't decode/,
+    'JSON decode fail',
+);
+
+mock 'HTTP::Tiny'
+    => methods 'get'
+    => should {
+        my $self = shift;
+        isa_ok( $self, 'HTTP::Tiny' );
+        is( $_[0], $mcpan->base_url . '/?test=it', 'Correct URL' );
+
+        return {
+            success => 1,
+            content => '{"content":"ok"}',
+        };
+    };
+
+is_deeply(
+    $mcpan->fetch( '', test => 'it' ),
+    { content => 'ok' },
+    'Sending params work right',
+);
+
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More tests => 4;
+use Test::Fatal;
+use MetaCPAN::API;
+
+my $mcpan = MetaCPAN::API->new;
+
+isa_ok( $mcpan, 'MetaCPAN::API' );
+can_ok( $mcpan, 'module'        );
+
+# missing input
+like(
+    exception { $mcpan->module },
+    qr/^Please provide a module name/,
+    'Missing any information',
+);
+
+my $result = $mcpan->module('Moose');
+ok( $result, 'Got result' );
+
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+use Test::Fatal;
+use MetaCPAN::API;
+
+my $mcpan = MetaCPAN::API->new;
+
+isa_ok( $mcpan, 'MetaCPAN::API' );
+can_ok( $mcpan, 'pod'           );
+my $errmsg = qr/^Either provide 'module' or 'author and 'release' and 'path'/;
+
+# missing input
+like(
+    exception { $mcpan->pod },
+    $errmsg,
+    'Missing any information',
+);
+
+# incorrect input
+like(
+    exception { $mcpan->pod( ding => 'dong' ) },
+    $errmsg,
+    'Incorrect input',
+);
+
+my $result = $mcpan->pod( module => 'Moose' );
+ok( $result, 'Got result' );
+
+$result = $mcpan->pod(
+    author => 'DOY', release => 'Moose-2.0201', path => 'lib/Moose.pm',
+);
+ok( $result, 'Got result' );
+
+# failing content types
+like(
+    exception {
+        $mcpan->pod( module => 'Moose', 'content-type' => 'text/text' )
+    },
+    qr/^Incorrect content-type provided/,
+    'Incorrect content-type',
+);
+
+# successful content types
+my @types = qw( text/html text/plain text/x-pod text/x-markdown );
+foreach my $type (@types) {
+    is(
+        exception { $mcpan->pod( module => 'Moose', 'content-type' => $type ) },
+        undef, # no exception
+        'Correct content-type',
+    );
+}
+
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More tests => 6;
+use Test::Fatal;
+use MetaCPAN::API;
+
+my $mcpan = MetaCPAN::API->new;
+
+isa_ok( $mcpan, 'MetaCPAN::API' );
+can_ok( $mcpan, 'release'       );
+my $errmsg = qr/^Either provide 'distribution', or 'author' and 'release', or 'search'/;
+
+# missing input
+like(
+    exception { $mcpan->release },
+    $errmsg,
+    'Missing any information',
+);
+
+# incorrect input
+like(
+    exception { $mcpan->release( ding => 'dong' ) },
+    $errmsg,
+    'Incorrect input',
+);
+
+my $result = $mcpan->release( distribution => 'Moose' );
+ok( $result, 'Got result' );
+
+$result = $mcpan->release(
+    author => 'DOY', release => 'Moose-2.0001'
+);
+
+ok( $result, 'Got result' );
@@ -0,0 +1,23 @@
+#!perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+use MetaCPAN::API;
+
+{
+    my $mcpan = MetaCPAN::API->new;
+    isa_ok( $mcpan->ua, 'HTTP::Tiny' );
+}
+
+{
+    my $mcpan = MetaCPAN::API->new(
+        ua_args => [ agent => 'MyAgentMon' ],
+    );
+
+    my $ua = $mcpan->ua;
+    isa_ok( $ua, 'HTTP::Tiny' );
+    is( $ua->agent, 'MyAgentMon', 'Correct user agent arguments' );
+}
+